package com.pansoft.hd;

import com.google.common.base.Joiner;
import com.pansoft.hd.model.BtcHdWallet;
import com.pansoft.hd.model.HDException;
import com.pansoft.hd.model.Messages;
import com.pansoft.openplanet.util.BtcNetParamUtil;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.crypto.MnemonicCode;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;

import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;

import static org.bitcoinj.crypto.HDUtils.parsePath;

/**
 * Created by yqs
 * Date: 2019/10/30
 * HD钱包，生成相关类
 * bip44 HD， btc 1开头的地址，eth
 * bip49 隔离见证 btc 3开头的地址
 * Time: 20:26
 * 测试网址 https://iancoleman.io/bip39/#english
 */
public class HDWalletUtil {
    /**
     * 获取网络类型
     *
     * @return
     */
    public static NetworkParameters getNetworkParameters() {
        return MainNetParams.get();
    }

    /**
     * 助记词数据变成String
     *
     * @param mnemonicList
     * @return
     */
    public static String mnemonic2String(List<String> mnemonicList) {
        return Joiner.on(" ").join(mnemonicList);
    }

    /**
     * 助记词String转换为 list
     *
     * @param mnemonics
     * @return
     */
    public static List<String> mnemonicString2List(String mnemonics) {
        return Arrays.asList(mnemonics.split(" "));
    }

    /**
     * 检验助记词是否合法
     *
     * @param mnemonicCodes
     */
    public static void validateMnemonics(List<String> mnemonicCodes) {
        try {
            MnemonicCode.INSTANCE.check(mnemonicCodes);
        } catch (org.bitcoinj.crypto.MnemonicException.MnemonicLengthException e) {
            throw new HDException(Messages.MNEMONIC_INVALID_LENGTH);
        } catch (org.bitcoinj.crypto.MnemonicException.MnemonicWordException e) {
            throw new HDException(Messages.MNEMONIC_BAD_WORD);
        } catch (Exception e) {
            throw new HDException(Messages.MNEMONIC_CHECKSUM);
        }
    }

    /**
     * 生成助记词
     *
     * @return
     */
    public static List<String> generateMnemonic() {
        try {
            SecureRandom secureRandom = new SecureRandom();
            /**必须是被4整除*/
            byte[] initialEntropy = new byte[16];
            secureRandom.nextBytes(initialEntropy);
            List<String> mnemonicList = MnemonicCode.INSTANCE.toMnemonic(initialEntropy);
            return mnemonicList;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 从助记词中获取 DeterministicSeed
     *
     * @param mnemonicList 助记词
     * @param passphrase   密码 密码建议使用方法空字符，都这样使用
     * @return
     */
    public static DeterministicSeed getDeterministicSeed(List<String> mnemonicList, String passphrase) {
        if (passphrase == null) {
            passphrase = "";
        }
        validateMnemonics(mnemonicList);
        long creationTimeSeconds = System.currentTimeMillis() / 1000;
        DeterministicSeed deterministicSeed = new DeterministicSeed(mnemonicList, null, passphrase, creationTimeSeconds);
        System.out.println("16进制seed值为：" + deterministicSeed.toHexString());
        return deterministicSeed;
    }


    /**
     * 生成随机的DeterministicSeed
     * 可以同时得到助记词
     */
    public static DeterministicSeed createRandomDeterministicSeed(String password) {
        DeterministicSeed deterministicSeed = new DeterministicSeed(new SecureRandom(), 128, password);
        System.out.println("16进制seed值为：" + deterministicSeed.toHexString());
        return deterministicSeed;
    }


    /**
     * 生成子地址
     *
     * @param bipPath bip格式，是否是bip44是隔离见证
     * @param xpub    mainAddressKey的xpub
     * @param nextIdx 生成的子地址序号
     * @return 子地址
     */
    public static BtcHdWallet.BtcChildAddress newReceiveAddress(String bipPath, String xpub, int nextIdx, NetworkParameters networkParameters) {
        DeterministicKey key = DeterministicKey.deserializeB58(xpub, networkParameters);
        DeterministicKey changeKey = HDKeyDerivation.deriveChildKey(key, ChildNumber.ZERO);
        DeterministicKey indexKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber(nextIdx));
        ECKey ecKey = ECKey.fromPublicOnly(indexKey.getPubKey());
        BtcHdWallet.BtcChildAddress btcChildAddress = new BtcHdWallet.BtcChildAddress();
        String address;
        //49表示隔离见证，3开头地址， bip49
        if (bipPath.contains("49")) {
            address = fromPrivateKey(ecKey, networkParameters).toString();
        } else {
            address = LegacyAddress.fromKey(networkParameters, ecKey).toBase58();
        }
        btcChildAddress.setAddress(address);
        btcChildAddress.setPath("0/" + nextIdx);
        btcChildAddress.setPublicKey(ecKey.getPublicKeyAsHex());
        return btcChildAddress;
    }

    /**
     * 获取私钥
     * @param mnemonic 助记词
     * @return
     */
    public static String getPrivateKey(String mnemonic) {
        DeterministicSeed ds = getDeterministicSeed(HDWalletUtil.mnemonicString2List(mnemonic), "");
        //deterministicKeyChain
        DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(ds).build();
        //主地址key
        DeterministicKey mainAddressKey = keyChain.getKeyByPath(parsePath(BtcNetParamUtil.getBtcBipPath() + "/0/0"), true);
        return mainAddressKey.getPrivateKeyEncoded(BtcNetParamUtil.getBtcNetParams()).toBase58();
    }

    /**
     * 根据path，获取子地址的私钥
     */
    public static String getChildPrivateKey(DeterministicKeyChain keyChain, String path, NetworkParameters parameters) {
        DeterministicKey deterministicKey = keyChain.getKeyByPath(parsePath(BtcNetParamUtil.getBtcBipPath() + path), true);
        return deterministicKey.getPrivateKeyEncoded(parameters).toBase58();
    }

    /**
     * 从私钥生成地址
     *
     * @param prvKeyHex 私钥
     */
    public static String fromPrivateKey(String prvKeyHex, NetworkParameters networkParameters) {
        ECKey key;
        if (prvKeyHex.length() == 51 || prvKeyHex.length() == 52) {
            DumpedPrivateKey dumpedPrivateKey = DumpedPrivateKey.fromBase58(networkParameters, prvKeyHex);
            key = dumpedPrivateKey.getKey();
            if (!key.isCompressed()) {
                throw new HDException(Messages.SEGWIT_NEEDS_COMPRESS_PUBLIC_KEY);
            }
        } else {
            key = ECKey.fromPrivate(NumericUtil.hexToBytes(prvKeyHex), true);
        }
        return calcSegWitAddress(key.getPubKeyHash(), networkParameters);
    }

    /***
     * 计算隔离见证地址
     */
    private static String calcSegWitAddress(byte[] pubKeyHash, NetworkParameters networkParameters) {
        String redeemScript = String.format("0x0014%s", NumericUtil.bytesToHex(pubKeyHash));
        return LegacyAddress.fromP2SHHash(networkParameters, Utils.sha256hash160(NumericUtil.hexToBytes(redeemScript))).toBase58();
    }

    /**
     * 从EcKey计算隔离见证地址
     */
    public static Address fromPrivateKey(ECKey ecKey, NetworkParameters networkParameters) {
        String redeemScript = String.format("0x0014%s", NumericUtil.bytesToHex(ecKey.getPubKeyHash()));
        return LegacyAddress.fromP2SHHash(networkParameters, Utils.sha256hash160(NumericUtil.hexToBytes(redeemScript)));
    }

}
